package controller

import (
	"context"
	"errors"
	v1 "k8s.io/api/apps/v1"
	v13 "k8s.io/api/core/v1"
	errors2 "k8s.io/apimachinery/pkg/api/errors"
	v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/dynamic/dynamicinformer"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"operator/pkg/format"
	"operator/pkg/jsonpatch"
	"operator/pkg/types/item"
	"operator/pkg/types/mesh"
	"operator/pkg/types/unstruct"
	"operator/pkg/util"
	"strings"
	"sync"
	"time"
)

const (
	mesh_podspec_path = "/spec/template"
	mesh_count_path   = "/spec/count"
)

type MeshController struct {
	ctx           context.Context
	mu            sync.RWMutex
	cfg           *rest.Config
	client        dynamic.Interface
	workerCount   int
	maxRetry      int
	informer      dynamicinformer.DynamicSharedInformerFactory
	queue         workqueue.RateLimitingInterface
	mapper        *restmapper.DeferredDiscoveryRESTMapper
	mLister       cache.GenericLister
	dlister       cache.GenericLister
	deplomentList map[string]struct{}
}

func (m *MeshController) Run(stopCh chan struct{}) {
	for i := 0; i < m.workerCount; i++ {
		go wait.Until(m.worker, time.Minute, stopCh)
	}
}

func (m *MeshController) worker() {
	for m.processNextItem() {
	}
}

func (m *MeshController) processNextItem() bool {
	it, shutdown := m.queue.Get()
	if shutdown {
		return false
	}
	defer m.queue.Done(it)

	err := m.syncMesh(it.(item.ItemOperation))
	if err != nil {
		format.Errors("pkg/controller/mesh.go", 72, err.Error())
		m.handlerError(it.(item.ItemOperation))
		return false
	}
	return true
}

func (m *MeshController) RemoveAll() {
	for k, _ := range m.deplomentList {
		arr := strings.Split(k, "/")
		gvr := m.getGVRFromObject(&v1.Deployment{})
		err := m.client.Resource(*gvr).Namespace(arr[0]).Delete(m.ctx, arr[1], v12.DeleteOptions{})
		if err != nil {
			format.Errors("pkg/controller/mesh.go", 85, err.Error())
		}
	}
}

func (m *MeshController) syncMesh(it item.ItemOperation) error {
	ns, name, err := cache.SplitMetaNamespaceKey(it.Item)
	if err != nil {
		return err
	}

	switch it.Operation {
	case item.ADD:
		ms, err := m.mLister.Get(it.Item)
		if err != nil {
			format.Errors("pkg/controller/mesh.go", 100, err.Error())
			return err
		}

		meshObj := &mesh.Mesh{}
		err = unstruct.UnstructToObject(ms.(*unstructured.Unstructured), meshObj)
		if err != nil {
			format.Errors("pkg/controller/mesh.go", 107, err.Error())
			return err
		}
		uni_name := util.GenerateName(name)

		m.mu.RLock()
		_, ok := m.deplomentList[ns+"/"+uni_name]
		m.mu.RUnlock()
		if !ok || it.RestartDeplyment {
			label := map[string]string{"mesh/app": uni_name}
			_, dname, err := m.createOrUpdateDeployment(meshObj, label, uni_name, it.RestartDeplyment)
			if err != nil {
				format.Errors("pkg/controller/mesh.go", 119, err.Error())
				return err
			}

			m.mu.Lock()
			defer m.mu.Unlock()
			m.deplomentList[ns+"/"+dname] = struct{}{}
		}
	case item.DEL:
		dname := util.GenerateName(name)

		m.mu.RLock()
		if _, ok := m.deplomentList[ns+"/"+dname]; !ok {
			format.Errors("pkg/controller/mesh.go", 132, "deployment not exists")
			return errors.New("deployment not exists")
		}
		m.mu.RUnlock()

		gvr := m.getGVRFromObject(&v1.Deployment{})
		err = m.client.Resource(*gvr).Namespace(ns).Delete(m.ctx, dname, v12.DeleteOptions{})
		if err != nil {
			format.Errors("pkg/controller/mesh.go", 140, err.Error())
			return err
		}

		m.mu.Lock()
		defer m.mu.Unlock()
		delete(m.deplomentList, ns+"/"+dname)
	case item.UPDATESTATUS:
		return nil
		ms, err := m.mLister.Get(it.Item)
		if err != nil {
			return err
		}

		meshObj := &mesh.Mesh{}
		err = unstruct.UnstructToObject(ms.(*unstructured.Unstructured), meshObj)
		if err != nil {
			return err
		}
		uni_name := util.GenerateName(name)

		m.mu.RLock()
		_, ok := m.deplomentList[ns+"/"+uni_name]
		m.mu.RUnlock()
		if ok {
			meshObj.Status.Status = &it.Status
			gvr := m.getGVRFromObject(&mesh.Mesh{})
			new := unstruct.ObjectToUnstruct(meshObj)
			_, err = m.client.Resource(*gvr).Namespace(ns).UpdateStatus(m.ctx, new, v12.UpdateOptions{})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

//need更新时是否需要更新deployment
func (m *MeshController) createOrUpdateDeployment(ms *mesh.Mesh, label map[string]string, name string, need bool) (ns string, dname string, err error) {
	newObj := ms.DeepCopy()
	if len(newObj.Spec.Template.Labels) > 0 {
		for k, v := range label {
			newObj.Spec.Template.Labels[k] = v
		}
	} else {
		newObj.Spec.Template.Labels = label
	}
	if newObj.Spec.Count == nil {
		return "", "", errors.New("empty data")
	}

	if newObj.Spec.Version == nil {
		str := "v1"
		newObj.Spec.Version = &str
	}

	for index, i := range newObj.Spec.Resources {
		if i.Version == nil {
			in, out := &newObj.Spec.Version, &newObj.Spec.Resources[index].Version
			*out = new(string)
			**out = **in
		}
		if i.Port == nil {
			in, out := &newObj.Spec.Port, &newObj.Spec.Resources[index].Port
			*out = new(int32)
			**out = **in
		}
	}

	if len(newObj.Spec.Template.Annotations) > 0 {
		newObj.Spec.Template.Annotations[util.ResourceAnnotation] = util.JsonEncode(newObj.Spec.Resources)
	} else {
		newObj.Spec.Template.Annotations = map[string]string{
			util.ResourceAnnotation: util.JsonEncode(newObj.Spec.Resources),
		}
	}

	namespace := "default"
	if newObj.Namespace != "" {
		namespace = newObj.Namespace
	}

	dname = name
	label2 := make(map[string]string)
	for k, v := range label {
		label2[k] = v
	}
	label2[util.ControlledAnnotation] = newObj.Name
	result := &v1.Deployment{
		ObjectMeta: v12.ObjectMeta{
			Name:      dname,
			Namespace: namespace,
			Labels:    label2,
		},
		Spec: v1.DeploymentSpec{
			Replicas: &*newObj.Spec.Count,
			Template: *newObj.Spec.Template,
			Selector: &v12.LabelSelector{
				MatchLabels: label,
			},
		},
	}

	gvr := m.getGVRFromObject(result)
	if gvr == nil {
		format.Errors("pkg/controller/mesh.go", 245, "get gvr error")
		return "", "", errors.New("errors")
	}

	data := unstruct.ObjectToUnstruct(result)
	_, err = m.client.Resource(*gvr).Namespace(namespace).Create(m.ctx, data, v12.CreateOptions{})
	if err != nil {
		if !errors2.IsAlreadyExists(err) {
			format.Errors("pkg/controller/mesh.go", 253, err.Error())
			return "", "", err
		}
		if !need {
			return namespace, dname, nil
		}
		_, err = m.client.Resource(*gvr).Namespace(namespace).Update(m.ctx, data, v12.UpdateOptions{})
		if err != nil {
			format.Errors("pkg/controller/mesh.go", 262, err.Error())
			return "", "", err
		}
	} else {
	}

	return namespace, dname, nil
}

func (m *MeshController) getGVR(gvk schema.GroupVersionKind) *schema.GroupVersionResource {
	restmapper, err := m.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil
	}

	return &restmapper.Resource
}

func (m *MeshController) getGVRFromObject(obj runtime.Object) *schema.GroupVersionResource {
	gvk := schema.GroupVersionKind{}
	switch obj.(type) {
	case *mesh.Mesh:
		gvk.Version = "v1"
		gvk.Group = "king.io"
		gvk.Kind = "mesh"
	case *v13.Pod:
		gvk.Version = "v1"
		gvk.Kind = "pod"
	case *v1.Deployment:
		gvk.Version = "v1"
		gvk.Group = "apps"
		gvk.Kind = "deployment"
	}

	restmapper, err := m.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil
	}

	return &restmapper.Resource
}

func (m *MeshController) handlerError(it item.ItemOperation) {
	if m.queue.NumRequeues(it) <= m.maxRetry {
		m.queue.AddRateLimited(it)
		return
	}

	m.queue.Forget(it)
}

func (m *MeshController) addMesh(obj interface{}) {
	ms, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		return
	}
	op := item.ItemOperation{
		Operation: item.ADD,
		Item:      ms,
	}
	m.queue.Add(op)
}

func (m *MeshController) delMesh(obj interface{}) {
	ms, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		return
	}
	op := item.ItemOperation{
		Operation: item.DEL,
		Item:      ms,
	}
	m.queue.Add(op)
}

func (m *MeshController) updateMesh(old, new interface{}) {
	op := item.ItemOperation{
		Operation: item.ADD,
	}
	pt := jsonpatch.JsonPatch(old, new)

	ms, err := cache.DeletionHandlingMetaNamespaceKeyFunc(new)
	if err != nil {
		return
	}
	op.Item = ms
	if pt.Empty() {
		return
	}
	if pt.Contains(mesh_podspec_path, true) || pt.Contains(mesh_count_path, false) {
		op.RestartDeplyment = true
	}

	m.queue.Add(op)
}

func (m *MeshController) updateChan(obj interface{}) {
	op := item.ItemOperation{}
	switch obj.(type) {
	case *v1.Deployment:
		if data, ok := obj.(*v1.Deployment).GetLabels()[util.ControlledAnnotation]; ok {
			op = item.ItemOperation{
				Operation: item.UPDATESTATUS,
				Status:    string(util.DeploymentStatus(obj.(*v1.Deployment))),
				Item:      obj.(*v1.Deployment).Namespace + "/" + data,
			}
			m.queue.Add(op)
		}
	case *unstructured.Unstructured:
		deployment := &v1.Deployment{}
		err := unstruct.UnstructToObject(obj.(*unstructured.Unstructured), deployment)
		if err != nil {
			return
		}
		if data, ok := deployment.GetLabels()[util.ControlledAnnotation]; ok {
			op = item.ItemOperation{
				Operation: item.UPDATESTATUS,
				Status:    string(util.DeploymentStatus(deployment)),
				Item:      deployment.Namespace + "/" + data,
			}

		}
	}
	if op.Item == "" {
		return
	}

	_, err := m.mLister.Get(op.Item)
	if err != nil {
		format.Errors("pkg/controller/mesh.go", 387, err.Error())
		return
	}

	m.queue.Add(op)
}

func NewMeshController(ctx context.Context,
	config *rest.Config,
	dSharedInformer dynamicinformer.DynamicSharedInformerFactory,
	mapper *restmapper.DeferredDiscoveryRESTMapper,
	client dynamic.Interface,
	workerCount int,
	maxRetry int,
) *MeshController {
	c := &MeshController{
		ctx:           ctx,
		cfg:           config,
		client:        client,
		maxRetry:      maxRetry,
		workerCount:   workerCount,
		informer:      dSharedInformer,
		mapper:        mapper,
		queue:         workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "meshController"),
		mu:            sync.RWMutex{},
		deplomentList: make(map[string]struct{}),
	}

	dSharedInformer.ForResource(*c.getGVRFromObject(&mesh.Mesh{})).Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.addMesh,
		UpdateFunc: c.updateMesh,
		DeleteFunc: c.delMesh,
	})

	dSharedInformer.ForResource(*c.getGVRFromObject(&v1.Deployment{})).Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: c.updateChan,
		UpdateFunc: func(oldObj, newObj interface{}) {
			c.updateChan(newObj)
		},
		DeleteFunc: c.updateChan,
	})

	c.dlister = dSharedInformer.ForResource(*c.getGVRFromObject(&v1.Deployment{})).Lister()
	c.mLister = dSharedInformer.ForResource(*c.getGVRFromObject(&mesh.Mesh{})).Lister()
	//c.plister = dSharedInformer.ForResource(*c.getGVRFromObject(&v13.Pod{})).Lister()
	//dSharedInformer.Start(wait.NeverStop)

	return c
}
